【step by step】使用vue3撸一个日历组件 您所在的位置:网站首页 行事历 app 【step by step】使用vue3撸一个日历组件

【step by step】使用vue3撸一个日历组件

2024-03-04 06:28| 来源: 网络整理| 查看: 265

日历组件大家都不陌生,在做一些中后台项目时,多数都会遇到。实际项目时,往往都会选择一套完整的UI框架,如Vue常用的ElementUI, React常用的antd Design等,这些组件库都会集成功能完善的日历组件,"拿来主义",我们会使用就好了。但是仔细想一下, 基本的日历组件以展示为主,交互并不复杂,那么我们能不能自己撸一个日历组件呢?接下来我们就一步一步, 分析该如何写出一个日历组件

一、核心功能

calendar-2.png

一个完整的日历展示大致如上图。包含三个区块:

月份日期; 上月末尾日期(红色框); 下月开头日期(蓝色框); 如果仅仅显示当月日期也是可以的, 但是日历上会有空白区域,不是那么美观, 通常的做法是用上月和下月的天数补齐, 因此我们这里采用42宫格的日历表,这样可以完整的展示任何月份的日历信息。 难点问题也围绕这三个, 如何正确渲染区块日期 计算原理:

1)获取当前月份的天数,计算出1号是周几,用于渲染当前月份的起始位置, 结束位置就是当月天数;2)补充上月末尾天数;上月末尾天数需要根据上月的天数和1号星期索引来计算: 上月天数 - 当月1号星期索引 + i(循环参数) + 1;3)下月开始天数,只需要知道当前日期表剩余的天数,从1号开始递增填补即可。

接下来我们就 step by step, 写出日历组件。

这里我们分两步走,首先先完成逻辑部分,要暴露出 generateCalendar(date: Date)函数, 这个函数最终会吐出完整的月份表的数据, 即42宫格数据,包括上月和下月的数据,然后根据数据,再写样式。

二、月份日期计算

月份日期计算要知道两点:这个月有几天,这个月1号是周几? 我们通过javascript的内置日期类:Date可以操作几乎所有日期相关内容。

2.1 月份天数

我们知道一年月份中,1,3,5,7,8,10,12月有31天,4,6,9,11月有30天,闰年2月29天,平年2月29天。所以我们需要两个函数:isLeap(year: number)和 getDays(year: number, month: year)来确定给日期的准确天数。

// 是否为闰年 const isLeap = (year: number) => { return (year % 4 === 0 && year % 100 !== 0) || year % 100 === 0; }; // 获取xx年xx月有几天 const getDays = (year: number, month: number): number => { const feb = isLeap(year) ? 29 : 28; const daysPerMonth = [31, feb, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; return daysPerMonth[month]; };

然后我们需要知道这个月1号是周几,这个我们可以通过Date.prototype.getDay()来获取。 注意:该API获取的星期是从周日开始,即0表示周日,其他以此递增。

我们这里使用的月份表示6x7的42宫格, 可以完整的展示月份信息。接下来我们来来构造generateCalendar(date: Date)函数, 先计算出当前月份的位置:

export interface CalendarItem { year: number; month: number; day: number; isCurrentMonth: boolean; } // ... export const generateCalendar = (date: Date) => { const currentYear = date.getFullYear(); const currentMonth = date.getMonth(); // 当月天数 const days = getDays(currentYear, currentMonth); // 1号是星期几 const weekIndex = new Date(`${currentYear}, ${currentMonth + 1}, 1`).getDay(); const calendarTable: CalendarItem[] = []; for (let i = 0; i < calendarGrid; i++) { if (i < weekIndex) { // 补充上月天数 } else if (i >= days + weekIndex) { // 补充下月天数 } } // 填充当月日期 for (let d = 1; d { const month = date.getMonth(); const year = date.getFullYear(); if (type === 'last') { const lastMonth = month === 0 ? 11 : month - 1; const lastYear = lastMonth === 11 ? year - 1 : year; return { year: lastYear, month: lastMonth, days: getDays(lastYear, lastMonth), }; } const nextMonth = month === 11 ? 0 : month + 1; const nextYear = nextMonth === 0 ? year + 1 : year; return { year: nextYear, month: nextMonth, days: getDays(nextYear, nextMonth), }; };

然后我们继续完成generateCalendar函数:

// ... const calendarGrid = 42; // 7 * 6宫格; const generateCalendar = (date: Date) => { const currentYear = date.getFullYear(); const currentMonth = date.getMonth(); // 当月天数 const days = getDays(currentYear, currentMonth); // 获取上月末尾天数和下月开头的天数,用于填补当月日历空白 const { days: lastMonthDays, year: lastMonthYear, month: lastMonth } = getNextOrLastMonthDays(date, 'last'); const { year: nextMonthYear, month: nextMonthMonth } = getNextOrLastMonthDays(date, 'next'); // 1号是星期几 const weekIndex = new Date(`${currentYear}, ${currentMonth + 1}, 1`).getDay(); // 显示在当月末尾的下月天数 const trailDays = calendarGrid - weekIndex - days; let trailVal = 0; const calendarTable: CalendarItem[] = []; for (let i = 0; i < calendarGrid; i++) { if (i < weekIndex) { // 补充上月天数 calendarTable[i] = { year: lastMonthYear, month: lastMonth, day: lastMonthDays - weekIndex + i + 1, isCurrentMonth: false, }; } else if (i >= days + weekIndex) { // 补充下月天数 if (trailVal < trailDays) { trailVal += 1; } calendarTable[i] = { year: nextMonthYear, month: nextMonthMonth, day: trailVal, isCurrentMonth: false, }; } } // 填充当月日期 for (let d = 1; d {{ dateText }} 今天 {{ item }} {{ item.day }}

这里说下一些细节功能的实现: 首先,我们要高亮当天日期,例如今天是1号,我们要在日历表里着重显示这一天。

// 当切换时间时, date是变化的 const date = ref(new Date()); const calendarTable = computed(() => generateCalendar(date.value)); // ... /** * 当天日期高亮显示, 兼容切换日期: * 年月日都要对上才能高亮 * ps: 日历可能会显示下月/上月的同样日期, 仅当月日期高亮 */ const isActive = (item: CalendarItem) => { return isAllTrue([ item.day === date.value.getDate(), item.isCurrentMonth, item.month === new Date().getMonth(), item.year === new Date().getFullYear(), ]); };

其次, 想要有个切换月份的功能, 即切换到上月或下月,这里有个逻辑,因为generateCalendar(date: Date)函数接收的是Date类型的参数,因此我们所有对日期的操作都将返回Date类型,即改变date这个参数,然后使用计算属性,会联动生成新的日历表。

至此, 一个基本的,带切换日期的基础日历就完成了🎉🎉

image.png

四、总结

这里一步一步的完成了一个基本日历组件,功能并不复杂。 这里做一下简单的复盘:

对Date类要有一定了解,起初觉得日历有些复杂的原因就是对Date类的用法不熟悉; 理清逻辑,对要实现的产品功能有一个清晰的认识。只有明白要做什么,才能去思考怎么做; 扩展性。 我们还要考虑组件的扩展性,通常我们写业务组件会更多一些, 可能这个业务组件只在某几个页面中使用,但是表现形式可能会有所不同,这时我们就要考虑怎样用少的代码,清晰的逻辑让组件易于使用和后期再扩展, 同时要保证组件代码是可维护的(该写注释的写注释,该解耦的解耦~),这样才是一个合格的组件。

最后贴上源码地址, 欢迎小伙伴们多多指教,多多star⭐️

github.com/CiroLee/vue…



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有